package com.example.foo.ftt.service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.transaction.annotation.Transactional;

import com.example.foo.ftt.model.FttProfit;
import com.example.foo.ftt.model.FttUser;
import com.example.foo.ftt.query.FttProfitQuery;
import com.example.foo.ftt.query.FttUserQuery;
import com.example.support.profile.ProfileHelper;
import com.example.support.redis.RedisHelper;
import com.example.foo.UserLevelEnum;
import com.example.core.PageResult;
import com.example.core.Pageable;
import com.example.foo.account.AccountHelper;
import com.example.foo.account.model.Account;
import com.example.foo.account.service.AccountService;
import com.example.foo.ftt.FttLevel;
import com.example.foo.ftt.FttProfitException;
import com.example.foo.ftt.FttUserException;

@org.springframework.stereotype.Service
public class FttProfitFacadeService {

	private final AccountService accountService;
	private final FttUserService fttUserService;
	private final FttProfitService fttProfitService;
	private final FttUserProfitService fttUserProfitService;

	public FttProfitFacadeService(AccountService accountService,
			FttUserService fttUserService,
			FttProfitService fttProfitService,
			FttUserProfitService fttUserProfitService) {
		this.accountService = accountService;
		this.fttUserService = fttUserService;
		this.fttProfitService = fttProfitService;
		this.fttUserProfitService = fttUserProfitService;
	}

	// ========== FttProfitFacade ==========

	public void updateFttProfits4Order(Long buyerUserId, Long itemId, Long orderId) {
		//TODO create fttorder, async process fttorder.

		updateFttUserUpgrade4Order(buyerUserId, itemId);

		updateFttProfitsIncrement4Order(itemId);
	}

	private void updateFttUserUpgrade4Order(Long buyerUserId, Long itemId) {
		FttUser buyerFttUser = fttUserService.findFttUserByUserId(buyerUserId);

		UserLevelEnum currentRoleLevel = UserLevelEnum.ofLevelId(buyerFttUser.getRoleLevel());
		UserLevelEnum nextRoleLevel = UserLevelEnum.L_2_1741;

		FttLevel currentFttLevel = buyerFttUser.getFttLevel();
		FttLevel nextFttLevel = FttLevel.F1;

		if (nextRoleLevel != null && currentRoleLevel.ordinal() < nextRoleLevel.ordinal()) {
			fttUserService.updateFttUserRoleLevel(buyerFttUser.getId(), nextRoleLevel);
		}

		if (currentFttLevel.ordinal() < nextFttLevel.ordinal()) {
			fttUserService.updateFttUserFttLevel(buyerFttUser.getId(), nextFttLevel);
		}
	}

	private void updateFttProfitsIncrement4Order(Long itemId) {
		Integer periodDay = AccountHelper.toPeriodDay(LocalDate.now());
		Map<FttLevel, BigDecimal> configMap = fttProfitMoneyConfig();

		for (FttLevel fttLevel : FttLevel.values()) {
			BigDecimal money = configMap.get(fttLevel);
			if (money == null || money.compareTo(BigDecimal.ZERO) <= 0) {
				continue;
			}

			BigDecimal totalMoney = money;
			updateFttProfitIncrement0(fttLevel, totalMoney, periodDay);
		}
	}

	@Transactional(timeout = 60, rollbackFor = Exception.class)
	public void updateFttProfitIncrement4CashOrder(Integer quantity) {
		Integer periodDay = AccountHelper.toPeriodDay(LocalDate.now());
		Map<FttLevel, BigDecimal> configMap = fttProfitMoneyConfig();
		List<FttLevel> fttLevels = Arrays.asList(FttLevel.F1, FttLevel.F2, FttLevel.F3, FttLevel.F4);
		for (FttLevel fttLevel : fttLevels) {
			BigDecimal money = configMap.get(fttLevel);
			BigDecimal totalMoney = money.multiply( new BigDecimal(quantity.toString()) );
			updateFttProfitIncrement0(fttLevel, totalMoney, periodDay);
		}
	}

	private void updateFttProfitIncrement0(FttLevel fttLevel, BigDecimal totalMoney, Integer periodDay) {
		//TODO lock
		FttProfit fttProfit = fttProfitService.findFttProfitByFttLevelAndPeriodDay(fttLevel, periodDay);
		if (fttProfit == null) {
			fttProfitService.createFttProfitPending(fttLevel, totalMoney, periodDay);
		} else {
			Long id = fttProfit.getId();
			fttProfitService.updateFttProfitIncrement(id, totalMoney);
		}
	}

	@Transactional(timeout = 60, rollbackFor = Exception.class)
	public void updateFttProfitSettle(Long id) {
		FttProfit fttProfit = fttProfitService.findFttProfitById( id);
		if (fttProfit.getState() != FttProfit.State.pending) {
			throw new FttProfitException(String.format("FttProfit#%s 状态错误: %s", fttProfit.getId(), fttProfit.getState().text));
		}

		FttLevel fttLevel = fttProfit.getFttLevel();
		BigDecimal totalMoney = fttProfit.getTotalMoney();
		Integer periodDay = fttProfit.getPeriodDay();

		Integer userCount = fttUserService.findFttUserCountByStateAndFttLevel(FttUser.State.normal, fttLevel).intValue();
		BigDecimal perUserMoney = BigDecimal.ZERO;

		if (totalMoney.compareTo(BigDecimal.ZERO) > 0 && userCount > 0) {
			perUserMoney = totalMoney.divide(new BigDecimal(userCount.toString()), 2, RoundingMode.FLOOR);

			FttUserQuery fttUserQuery = new FttUserQuery();
			fttUserQuery.setState(FttUser.State.normal);
			fttUserQuery.setFttLevel(fttLevel);

			for (Pageable fttUserPageable = new Pageable(1, 100); ; fttUserPageable = fttUserPageable.next()) {
				PageResult<FttUser> fttUserPageResult = fttUserService.findFttUsers(fttUserQuery, fttUserPageable);
				List<FttUser> fttUsers = fttUserPageResult.getRows();
				if (fttUsers.isEmpty()) {
					break;
				}

				for (FttUser fttUser : fttUsers) {
					try {
						BigDecimal money = perUserMoney;
						Long userId = fttUser.getUserId();
						String cellphone = fttUser.getCellphone();
						fttUserProfitService.createFttUserProfit(fttLevel, money, periodDay, userId, cellphone);

						String name = fttLevel.text + "分红";
						BigDecimal value = money;
						String relType = "FttProfit";
						Long relId = fttProfit.getId();
						accountService.updateAccountIncrement(Account.Acctc.fttProfit, name, value, userId, relType, relId);
						accountService.updateAccountIncrement(Account.Acctc.pinmoney, name, value, userId, relType, relId);
					} catch (Exception e) {
						throw new FttUserException(String.format("FttUser#%s", fttUser.getId()), e);
					}
				}
			}

			fttProfitService.updateFttProfitSuccess(id, userCount, perUserMoney);
		} else {
			fttProfitService.updateFttProfitFailure(id);
		}
	}

	@Transactional(timeout = 60, rollbackFor = Exception.class)
	public void createFttWeekProfitSettle() {
		final FttLevel fttLevel = FttLevel.F3;
		BigDecimal totalMoney = totalFttWeekProfit();
		if (totalMoney.compareTo(BigDecimal.ZERO) <= 0) {
			return;
		}
		Integer periodDay = AccountHelper.toPeriodDay(LocalDate.now());

		String lockKey = String.format("lock:FttWeekProfit:periodDay_%s", periodDay);
		if (!RedisHelper.setnxex(lockKey, Duration.ofDays(5).getSeconds(), "-")) {
			throw new FttUserException("Failed to acquire lock");
		}

		Integer userCount = fttUserService.findFttUserCountByStateAndFttLevel(FttUser.State.normal, fttLevel).intValue();
		if (userCount == 0) {
			return;
		}
		BigDecimal perUserMoney = totalMoney.divide(new BigDecimal(userCount.toString()), 2, RoundingMode.FLOOR);

		FttUserQuery fttUserQuery = new FttUserQuery();
		fttUserQuery.setState(FttUser.State.normal);
		fttUserQuery.setFttLevel(fttLevel);

		for (Pageable fttUserPageable = new Pageable(1, 100); ; fttUserPageable = fttUserPageable.next()) {
			PageResult<FttUser> fttUserPageResult = fttUserService.findFttUsers(fttUserQuery, fttUserPageable);
			List<FttUser> fttUsers = fttUserPageResult.getRows();
			if (fttUsers.isEmpty()) {
				break;
			}

			for (FttUser fttUser : fttUsers) {
				try {
					BigDecimal money = perUserMoney;
					Long userId = fttUser.getUserId();

					String name = fttLevel.text + "周分红";
					BigDecimal value = money;
					String relType = "FttWeekProfit";
					Long relId = 0L;
					accountService.updateAccountIncrement(Account.Acctc.fttProfit, name, value, userId, relType, relId);
					accountService.updateAccountIncrement(Account.Acctc.pinmoney, name, value, userId, relType, relId);
				} catch (Exception e) {
					throw new FttUserException(String.format("FttUser#%s", fttUser.getId()), e);
				}
			}
		}

		RedisHelper.setex("FttWeekProfit:total", Duration.ofDays(10).getSeconds(), totalMoney.toString());
	}

	private BigDecimal totalFttWeekProfit() {
		final FttLevel fttLevel = FttLevel.F3;
		Map<FttLevel, BigDecimal> configMap = fttProfitMoneyConfig();

		final BigDecimal perOrderFttWeekProfit = new BigDecimal("50.00");
		final BigDecimal perOrderFttProfit = configMap.get(fttLevel);
		BigDecimal totalMoney;

		Integer periodDayFrom = AccountHelper.toPeriodDay(LocalDate.now().plusDays(-7));
		Integer periodDayTo = AccountHelper.toPeriodDay(LocalDate.now().plusDays(-1));

		FttProfitQuery fttProfitQuery = new FttProfitQuery();
		fttProfitQuery.setFttLevel(fttLevel);
		fttProfitQuery.setPeriodDayFrom(periodDayFrom);
		fttProfitQuery.setPeriodDayTo(periodDayTo);

		PageResult<FttProfit> fttProfitPageResult = fttProfitService.findFttProfits(fttProfitQuery, new Pageable(1, 7));
		totalMoney = fttProfitPageResult.getRows()
				.stream()
				.map(FttProfit::getTotalMoney)
				.reduce(BigDecimal.ZERO, BigDecimal::add);

		return totalMoney.divide(perOrderFttProfit, 0, RoundingMode.FLOOR).multiply(perOrderFttWeekProfit);
	}

	private Map<FttLevel, BigDecimal> fttProfitMoneyConfig() {
		// {"F1":"450.00","F2":"400.00","F3":"200.00","F4":"200.00","F5":"100.00","F6":"100.00","F7":"100.00"}
		Map<String, Object> configMap = ProfileHelper.lookupValueAsMap("ftt.FttProfit.money");
		Map<FttLevel, BigDecimal> resultMap = new HashMap<>(configMap.size());
		configMap.forEach((k, v) -> {
			resultMap.put(FttLevel.valueOf(k), new BigDecimal(v.toString()));
		});
		return resultMap;
	}

}
